from types import FunctionType
from django import forms
from django.urls import path, re_path, reverse
from django.utils.safestring import mark_safe
from django.shortcuts import HttpResponse, render, redirect
from django.http import QueryDict
from django.db.models import Q, ForeignKey, ManyToManyField
from .pagination import Pagination
from functools import wraps


def choice_display(title, field):
    """
    用于显示 model类中 choice相关字段
    :param title: 表头名
    :param field: model类中 choice相关字段
    :return: 表头名 or choice中的中文
    """

    def inner(self, obj=None, is_head=None, *args, **kwargs):
        if is_head:
            return title
        method = 'get_%s_display' % field
        return getattr(obj, method)()

    return inner


def m2m_display(title, field):
    """
    用于显示 many to many字段在页面中的显示
    :param title: 表头名
    :param field: model类中 m2m相关字段
    :return:
    """

    def inner(self, obj=None, is_head=None, *args, **kwargs):
        if is_head:
            return title
        q_set = getattr(obj, field).all()
        li = [str(row) for row in q_set]
        return ', '.join(li)

    return inner


class SearchGroupData:
    def __init__(self, title, data, s_option, query_dict):
        """
        :param title: 组合搜索行 标题
        :param data: choice列表或 queryset
        :param s_option: SearchOption对象，SearchGroupData对象与 SearchOption对象 一一关联
        """
        self.title = title
        self.data = data
        self.s_option = s_option
        self.query_dict = query_dict

    def __iter__(self):
        yield '<div class="search-head">'
        yield self.title + ':'
        yield '</div>'

        yield '<div class="search-body">'

        total_query_dict = self.query_dict.copy()
        o_value_list = total_query_dict.getlist(self.s_option.field)
        if o_value_list:
            total_query_dict.pop(self.s_option.field)
            yield '<a href="?%s">全部</a>' % total_query_dict.urlencode()
        else:
            yield '<a class="active">全部</a>'

        for item in self.data:
            text = self.s_option.get_text(item)
            value = str(self.s_option.get_value(item))
            query_dict = self.query_dict.copy()  # 深拷贝
            o_value_list = query_dict.getlist(self.s_option.field)
            query_dict._mutable = True

            if not self.s_option.is_multi:  # 不支持多选
                query_dict[self.s_option.field] = value
                if value in o_value_list:
                    query_dict.pop(self.s_option.field)
                    yield '<a class="active" href="?%s">%s</a>' % (query_dict.urlencode(), text)
                else:
                    yield '<a href="?%s">%s</a>' % (query_dict.urlencode(), text)
            else:  # 支持多选
                if value in o_value_list:
                    o_value_list.remove(value)
                    query_dict.setlist(self.s_option.field, o_value_list)
                    yield '<a class="active" href="?%s">%s</a>' % (query_dict.urlencode(), text)
                else:
                    o_value_list.append(value)
                    query_dict.setlist(self.s_option.field, o_value_list)
                    yield '<a href="?%s">%s</a>' % (query_dict.urlencode(), text)

        yield '</div>'


class SearchOption:
    def __init__(self, field, db_condition=None, is_multi=False, text_func=None, value_func=None):
        """
        :param field: model类的某个字段
        :param db_condition: 数据库表筛选条件
        :param text_func: 自定义函数，返回组合搜索页面按钮的文本
        :param value_func: 自定义函数，返回组合搜索页面按钮的值
        :param is_multi: 组合搜索是否支持多选
        """
        self.field = field
        self.is_multi = is_multi
        if not db_condition:
            db_condition = {}
        self.db_condition = db_condition
        self.text_func = text_func
        self.is_choice = False
        self.value_func = value_func

    def get_db_condition(self, request, *args, **kwargs):
        return self.db_condition

    def get_data(self, model_class, request, *args, **kwargs):
        field_object = model_class._meta.get_field(self.field)
        if isinstance(field_object, ForeignKey) or isinstance(field_object, ManyToManyField):
            db_condition = self.get_db_condition(request, *args, **kwargs)
            return SearchGroupData(field_object.verbose_name, field_object.related_model.objects.filter(**db_condition),
                                   self, request.GET)
        else:
            self.is_choice = True
            return SearchGroupData(field_object.verbose_name, field_object.choices, self, request.GET)

    def get_text(self, item):  # 返回组合搜索页面按钮的文本
        if self.text_func:
            return self.text_func(item)

        if self.is_choice:
            return item[1]
        else:
            return str(item)

    def get_value(self, item):  # 返回组合搜索页面按钮的值
        if self.value_func:
            return self.value_func(item)

        if self.is_choice:
            return item[0]
        else:
            return item.pk


class StarkHandler:
    list_display = []
    row_count = 15
    has_add_btn = True  # 默认有添加按钮
    model_form_class = None
    my_sort = []
    search_col_list = []
    action_list = []
    search_group = []
    list_template = None

    def __init__(self, site, model_class, prev):
        self.model_class = model_class
        self.prev = prev
        self.site = site
        self.request = None

    def multi_delete(self, request, *args, **kwargs):
        id_list = request.POST.getlist('cb')
        self.model_class.objects.filter(id__in=id_list).delete()

    multi_delete.text = '批量删除'

    def get_search_group(self):
        return self.search_group

    def get_action_list(self):
        return self.action_list

    def get_search_col_list(self):
        return self.search_col_list

    def get_sort_list(self):
        return self.my_sort or ['id']

    def btn_add_url(self, *args, **kwargs):  # 生成添加按钮所需 url
        base_url = reverse('%s:%s' % (self.site.namespace, self.get_add_url_name), args=args, kwargs=kwargs)
        if not self.request.GET:
            add_url = base_url
        else:
            add_url_dict = QueryDict(mutable=True)
            add_url_dict['_addi'] = self.request.GET.urlencode()
            add_url = '%s?%s' % (base_url, add_url_dict.urlencode())

        return add_url

    def create_re_url(self, name, nid):
        base_url = reverse('%s:%s' % (self.site.namespace, self.get_url_name(name)), args=(nid,))
        if not self.request.GET:
            r_url = base_url
        else:
            r_url_dict = QueryDict(mutable=True)
            r_url_dict['_addi'] = self.request.GET.urlencode()
            r_url = '%s?%s' % (base_url, r_url_dict.urlencode())

        return r_url

    def get_add_btn(self, request, *args, **kwargs):
        if self.has_add_btn:
            return '<a class="btn btn-primary" href="%s">添加</a>' % self.btn_add_url(*args, **kwargs)
        return None

    def display_edit(self, obj=None, is_head=None, *args, **kwargs):  # 自定义函数在页面中显示新的一列
        if is_head:
            return '编辑'

        base_url = reverse('%s:%s' % (self.site.namespace, self.get_edit_url_name), args=(obj.id,))
        if not self.request.GET:
            edit_url = base_url
        else:
            edit_url_dict = QueryDict(mutable=True)
            edit_url_dict['_addi'] = self.request.GET.urlencode()
            edit_url = '%s?%s' % (base_url, edit_url_dict.urlencode())

        return mark_safe("<a href='%s'>编辑</a>" % edit_url)

    def display_checkbox(self, obj=None, is_head=None, *args, **kwargs):  # 自定义函数在页面中显示新的一列
        if is_head:
            return '选择'

        return mark_safe('<input type="checkbox" name="cb" value="%s" />' % obj.id)

    def display_del(self, obj=None, is_head=None, *args, **kwargs):
        if is_head:
            return '删除'

        base_url = reverse('%s:%s' % (self.site.namespace, self.get_delete_url_name), args=(obj.id,))
        if not self.request.GET:
            del_url = base_url
        else:
            del_url_dict = QueryDict(mutable=True)
            del_url_dict['_addi'] = self.request.GET.urlencode()
            del_url = '%s?%s' % (base_url, del_url_dict.urlencode())

        return mark_safe("<a href='%s'>删除</a>" % del_url)

    def get_model_form_class(self, add_or_edit, request, *args, **kwargs):
        if self.model_form_class:
            return self.model_form_class

        class ChangeModelForm(forms.ModelForm):
            class Meta:
                model = self.model_class
                fields = '__all__'

            def __init__(self, *args, **kwargs):
                super().__init__(*args, **kwargs)
                for name, f in self.fields.items():  # 添加样式
                    f.widget.attrs['class'] = 'form-control'

        return ChangeModelForm

    def get_search_group_condition(self, request):  # 获取组合搜索的条件
        condition = {}
        for option in self.get_search_group():
            value_list = request.GET.getlist(option.field)
            if not value_list:
                continue
            condition['%s__in' % option.field] = value_list

        return condition

    def back_list_url(self, *args, **kwargs):
        url_name = '%s:%s' % (self.site.namespace, self.get_list_url_name)
        base_url = reverse(url_name, args=args, kwargs=kwargs)
        params = self.request.GET.get('_addi')
        if params:
            re_url = '%s?%s' % (base_url, params)
        else:
            re_url = base_url

        return re_url

    def save(self, request, form_obj, is_update, *args, **kwargs):
        form_obj.save()

    def add_view(self, request, *args, **kwargs):
        model_form_class = self.get_model_form_class('add', request, *args, **kwargs)

        if request.method == 'GET':
            form_obj = model_form_class()
            return render(request, 'stark/change.html', {'my_form': form_obj})

        if request.method == 'POST':
            form_obj = model_form_class(data=request.POST)
            if form_obj.is_valid():
                self.save(request, form_obj, False, *args, **kwargs)

                return redirect(self.back_list_url(*args, **kwargs))
            else:
                return render(request, 'stark/change.html', {'my_form': form_obj})

    def delete_obj(self, request, nid, *args, **kwargs):
        self.model_class.objects.filter(id=nid).delete()

    def delete_view(self, request, nid, *args, **kwargs):
        origin_url = self.back_list_url(*args, **kwargs)
        if request.method == 'GET':

            return render(request, 'stark/delete.html', {'cancel': origin_url})

        if request.method == 'POST':
            self.delete_obj(request, nid, *args, **kwargs)

            return redirect(origin_url)

    def get_edit_obj(self, request, nid, *args, **kwargs):
        obj = self.model_class.objects.filter(id=nid).first()

        return obj

    def edit_view(self, request, nid, *args, **kwargs):  # 改
        obj = self.get_edit_obj(request, nid, *args, **kwargs)
        if not obj:
            return HttpResponse('404 not found')

        model_form_class = self.get_model_form_class('edit', request, *args, **kwargs)

        if request.method == 'GET':
            form_obj = model_form_class(instance=obj)
            return render(request, 'stark/change.html', {'my_form': form_obj})

        if request.method == 'POST':
            form_obj = model_form_class(data=request.POST, instance=obj)
            if form_obj.is_valid():
                self.save(request, form_obj, True, *args, **kwargs)

                return redirect(self.back_list_url(*args, **kwargs))
            else:
                return render(request, 'stark/change.html', {'my_form': form_obj})

    def get_list_display(self, request, *args, **kwargs):
        """
        如在自定义 Handler类中写了该方法，则可自定义 页面上显示的列
        :return: 页面上显示的列
        """
        re_list = []
        re_list.extend(self.list_display)

        return re_list

    def get_query_set(self, request, *args, **kwargs):
        first_q_set = self.model_class.objects.all()
        return first_q_set

    def list_view(self, request, *args, **kwargs):  # 查
        #  begin 批量操作
        action_list = self.get_action_list()
        action_dict = {item.__name__: item.text for item in action_list}

        if request.method == 'POST':
            action_func_name = request.POST.get('multi_action')
            if action_func_name in action_dict:
                action_func = getattr(self, action_func_name)
                action_func(request, *args, **kwargs)
        #  end 批量操作

        # begin 组合搜索
        search_group = self.get_search_group()
        search_group_data_list = []
        for item in search_group:
            tmp_obj = item.get_data(self.model_class, request, *args, **kwargs)
            search_group_data_list.append(tmp_obj)

        search_group_condition = self.get_search_group_condition(request)  # 获取组合搜索的条件
        # end 组合搜索

        #  begin 关键字搜索
        search_col_list = self.get_search_col_list()
        k = request.GET.get('k', '')
        rule = Q()
        rule.connector = 'OR'
        if k:
            for item in search_col_list:
                rule.children.append((item, k))
        #  end 关键字搜索

        sort_list = self.get_sort_list()  # 获取排序规则列表

        # begin 处理分页
        all_count = self.model_class.objects.all().count()
        query_params = request.GET.copy()
        query_params._mutable = True

        page = Pagination(
            current_page=request.GET.get('page'),
            all_count=all_count,
            base_url=request.path_info,
            query_params=query_params
        )
        # end 处理分页

        head_list = []  # 列表元素为字符串
        body_list = []  # 大列表套小列表
        list_display = self.get_list_display(request, *args, **kwargs)

        if list_display:
            for item in list_display:
                if isinstance(item, FunctionType):
                    head_list.append(item(self, obj=None, is_head=True, *args, **kwargs))
                else:
                    head_list.append(self.model_class._meta.get_field(item).verbose_name)
        else:
            head_list.append(self.model_class._meta.model_name)

        first_query_set = self.get_query_set(request, *args, **kwargs)
        obj_set = first_query_set.filter(**search_group_condition).filter(rule).order_by(*sort_list)[
                  page.start: page.end]
        for row in obj_set:
            tmp_list = []
            if list_display:
                for ele in list_display:
                    if isinstance(ele, FunctionType):
                        tmp_list.append(ele(self, obj=row, is_head=False, *args, **kwargs))
                    else:
                        tmp_list.append(getattr(row, ele))
            else:
                tmp_list.append(row)

            body_list.append(tmp_list)

        add_btn = self.get_add_btn(request, *args, **kwargs)

        return render(request,
                      self.list_template or 'stark/list.html',
                      {
                          'head_list': head_list,
                          'body_list': body_list,
                          'page': page,
                          'add_btn': add_btn,
                          'search_col_list': search_col_list,
                          'k': k,
                          'action_dict': action_dict,
                          'search_group_data_list': search_group_data_list
                      })

    def get_url_name(self, param):
        app_name = self.model_class._meta.app_label
        model_name = self.model_class._meta.model_name
        if self.prev:
            return '%s_%s_%s_%s' % (app_name, model_name, self.prev, param)
        else:
            return '%s_%s_%s' % (app_name, model_name, param)

    @property
    def get_list_url_name(self):
        return self.get_url_name('list')

    @property
    def get_add_url_name(self):
        return self.get_url_name('add')

    @property
    def get_edit_url_name(self):
        return self.get_url_name('edit')

    @property
    def get_delete_url_name(self):
        return self.get_url_name('delete')

    def wrapper(self, func):  # 装饰器函数
        @wraps(func)  # 保留原函数信息
        def inner(request, *args, **kwargs):
            self.request = request
            return func(request, *args, **kwargs)

        return inner

    def get_urls(self):
        patterns = [
            path('list/', self.wrapper(self.list_view), name=self.get_list_url_name),
            path('add/', self.wrapper(self.add_view), name=self.get_add_url_name),
            re_path('edit/(?P<nid>\d+)/$', self.wrapper(self.edit_view), name=self.get_edit_url_name),
            re_path('delete/(?P<nid>\d+)/$', self.wrapper(self.delete_view), name=self.get_delete_url_name)
        ]

        patterns.extend(self.extra_urls())

        return patterns

    def extra_urls(self):
        return []


class StarkSite:
    def __init__(self):
        self._registry = []
        self.app_name = 'stark'
        self.namespace = 'stark'

    def register(self, model_class, handler_class=None, prev=None):
        """
        :param prev: 生成的 url的前缀
        :param model_class: model中的类（表）
        :param handler_class: 处理请求（增删改查）的类
        :return:
        """
        if not handler_class:
            handler_class = StarkHandler

        self._registry.append({'model_class': model_class, 'handler': handler_class(self, model_class, prev),
                               'prev': prev})

    def get_urls(self):
        patterns = []
        for item in self._registry:
            model_class = item['model_class']
            handler = item['handler']
            prev = item['prev']
            if prev:
                tmp_url = '%s/%s/%s/' % (model_class._meta.app_label, model_class._meta.model_name, prev)
            else:
                tmp_url = '%s/%s/' % (model_class._meta.app_label, model_class._meta.model_name)

            tmp_path = path(tmp_url, (handler.get_urls(), None, None))

            patterns.append(tmp_path)

        return patterns

    @property
    def urls(self):
        return self.get_urls(), self.app_name, self.namespace


site = StarkSite()
